import math
import os
from io import BytesIO  ### 创建内存对象使用的
from os.path import exists

from django.db.models import F
from django.http import HttpResponse
from django.shortcuts import render, redirect
from django.template.base import render_value_in_context
from django.template.defaultfilters import title
from django.utils.datetime_safe import datetime
from importlib_metadata import files
from systemd.journal import stream

from app_01 import models
from django import forms # forms.ModelForm使用

from django.core.exceptions import ValidationError ## raise抛出异常

from app_01.utils.bootstrap import BootStrapModelForm, BootStrapForm
from app_01.utils.encrypt import md5
from app_01.utils.code import check_code
from app_01.utils.pagination import Pagination


# Create your views here.
def pretty_list(request):
    ## 添加测试用的数据
    # for i in range(50):
    #     models.PrettyNum.objects.create(mobile='123456789', price='777777', level=4, status=2)

    ## request.GET获取url中携带的全部参数,获取的是一个字典数据。获取数值：for k in request.GET.values()，获取键：for k in request.GET
    ## request.GET.urlencode() 获取的是url后面拼接的所有的参数
    ### 因为request禁止修改，所以需要copy一份来修改

    # import copy
    # query_dict = copy.deepcopy(request.GET)
    # query_dict._mutable = True
    # query_dict.setlist('mmm',[77,88])
    # print(query_dict.urlencode())


    ## 添加查询用的字典
    data_dict = {}
    search_data = request.GET.get('q','') ## 当url中？后面的参数q=有数值的时候，search_data会获取q的数据，
                                          ## 当q没有数据或着没有q=时，search_data=''

    data_dict['mobile__contains'] = search_data


    queryset = models.PrettyNum.objects.filter(**data_dict).order_by('-id')# 根据字段的值进行排序，带着负号是降序。获取列表中的索引的数据

    # quest_dict = request.GET
    # print(quest_dict)

    ### 导入写的分页组件的类，并把需要的参数传递过去
    from app_01.utils.pagination import Pagination
    page_object = Pagination(request,queryset) ## 把查询出来的数据传递到分页类中
    page_queryset = page_object.page_queryset  ## 获取分完页面的数据
    page_string = page_object.html()  ## 获取生成的页码
    context = {
                'queryset':page_queryset, ## 分完页后的数据
               'page_string':page_string, ## 生成的页码
               'search_data':search_data  ## 要查询的数据
               }
    return render(request,'pretty_list.html',context)


class PrettyModelForm(forms.ModelForm):
    ##校验方式一：手动书写校验
    # mobile = forms.CharField(
    #     label='手机号',
    #     validators=[RegexValidator(r'^1[3-9]\d{9}$', '输入的手机号格式错误')] # 正则表达式可以写多个，都放在这个列表中
    # )
    class Meta:
        model = models.PrettyNum # 关联models.py中的表格
        fields = '__all__' # 可以用列表列出所有的列，也可以用这个来代表显示所有的列
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        for name,field in self.fields.items(): # name是models.py中表PrettyNum中定义的字段的名字如：mobile,price,level等
                                              # name数据的作用是：可以通过比较只让那几个字段更改插件属性。如：if name == 'mobile': 。。。。。
                                             # field是列对象，里面包含列对象的样式等。
            if field.widget.attrs: # 判断是否存在其它的插件的属性，如果存在就只把这俩属性class和placeholder更改，其余的保持不变
                field.widget.attrs['class'] = "form-control"
                field.widget.attrs['placeholder'] = field.label
            else: # 如果不存在插件属性，那么就直接赋值就可以。
                field.widget.attrs = {'class': "form-control",'placeholder': field.label}

    #### 校验方式二：利用字段的钩子方法。例如：利用手机号字段的钩子方法来验证手机号
    def clean_mobile(self):
        txt_mobile = self.cleaned_data['mobile'] ## clean_data中保存着校验好的数据
        exists = models.PrettyNum.objects.filter(mobile=txt_mobile).exists()
        if exists:
            raise ValidationError('手机号码已存在！')
        if len(txt_mobile) != 11:
            raise ValidationError('手机号码格式错误')
        return txt_mobile #校验通过的话就返回这个数据

def pretty_add(request):

    if request.method == 'GET':
        form = PrettyModelForm()
        return render(request,'pretty_add.html',{'form':form})
    form = PrettyModelForm(data=request.POST)
    if form.is_valid():
        form.save()
        return redirect('/pretty/list/')
    return render(request, 'pretty_add.html', {'form': form})


class PrettyEditModelForm(BootStrapModelForm):
    ##校验方式一：手动书写校验
    # mobile = forms.CharField(
    #     label='手机号',
    #     validators=[RegexValidator(r'^1[3-9]\d{9}$', '输入的手机号格式错误')] # 正则表达式可以写多个，都放在这个列表中
    # )
    class Meta:
        model = models.PrettyNum # 关联models.py中的表格
        fields = '__all__' # 可以用列表列出所有的列，也可以用这个来代表显示所有的列


    #### 校验方式二：利用字段的钩子方法。例如：利用手机号字段的钩子方法来验证手机号
    def clean_mobile(self):
        txt_mobile = self.cleaned_data['mobile'] ## clean_data中保存着校验好的数据
        idd = self.instance.pk ## pk是当前对象的primarykey，也就是id的值。获取当前对象指向的row_object，在下面的pretty_edit函数中已经通过
                             ## form = PrettyModelForm(data=request.POST,instance=row_object)，把对象row_object赋值给了instance
        exists = models.PrettyNum.objects.exclude(id=idd).filter(mobile=txt_mobile).exists() ## 排除自己的id意外，没有重复的
        if exists:
            raise ValidationError('手机号码已存在！')
        if len(txt_mobile) != 11:
            raise ValidationError('手机号码格式错误')
        return txt_mobile #校验通过的话就返回这个数据

def pretty_edit(request,nid):
    row_object = models.PrettyNum.objects.filter(id=nid).first()
    if request.method == 'GET':
        form = PrettyEditModelForm(instance=row_object)
        return render(request,'pretty_edit.html',{'form':form})
    form = PrettyEditModelForm(data=request.POST,instance=row_object)
    if form.is_valid():
        form.save()
        return redirect('/pretty/list/')
    return render(request, 'pretty_edit.html', {'form': form})


def pretty_delete(request,nid):
    models.PrettyNum.objects.filter(id=nid).delete()
    return redirect('/pretty/list/')

def admin_list(request):

    ## 要进入到admin/list/ 先判断用户是否已经登录过，是的话显示页面，不是的话重定向到登录页面
    ## 需要用get来获取数据，如果直接用：request.session['info']来获取数据的话，如果未获取到将报错。用get获取数据的话，没有数据返回的是none
    # info = request.session.get('info')
    # if not info: ## 在session中找不到cookie的随机字符串，那么info就会等于None
    #     return redirect('/login/')

    ## 添加查询用的字典
    data_dict = {}
    search_data = request.GET.get('q','') ## 当url中？后面的参数q=有数值的时候，search_data会获取q的数据，
                                          ## 当q没有数据或着没有q=时，search_data=''

    data_dict['username__contains'] = search_data


    queryset = models.Admin.objects.filter(**data_dict).order_by('-id')# 根据字段的值进行排序，带着负号是降序。获取列表中的索引的数据
    from app_01.utils.pagination import Pagination
    page_object = Pagination(request, queryset)  ## 把查询出来的数据传递到分页类中
    page_queryset = page_object.page_queryset  ## 获取分完页面的数据
    page_string = page_object.html()  ## 获取生成的页码
    context = {
        'queryset': page_queryset,  ## 分完页后的数据
        'page_string': page_string,  ## 生成的页码
        'search_data': search_data  ## 要查询的数据
    }

    return render(request,'admin_list.html',context)

class adminModelForm(BootStrapModelForm):

    confirm_password = forms.CharField(label='确认密码',max_length=64,
                                       widget=forms.PasswordInput(render_value=True))


    class Meta:
        model = models.Admin # 关联models.py中的表格
        fields = '__all__' # 可以用列表列出所有的列，也可以用这个来代表显示所有的列
        widgets = {
            'password':forms.PasswordInput(render_value=True)   ### 定义原来的密码输入框的样式也是：PasswordInput。手动生成的输入框在上面定义，自动生成的在这里定义样式
        }

    #### 利用字段的钩子方法,验证两次输入的密码是否一致。使用那个字段的钩子函数，就在那个字段的下面抛出异常提示
    def clean_confirm_password(self):
        password = self.cleaned_data['password'] ## 获取第一次输入的原密码。clean_data中保存着校验好的所有的数据
        confirm_password = md5(self.cleaned_data['confirm_password']) ## 获取第二次输入的原密码
        if password != confirm_password:
            raise ValidationError('两次输入的密码不一致！')
        return confirm_password #校验通过的话就返回第二次输入的密码

    def clean_password(self): # 让数据库保存经过md5加密后的密码
        password = md5(self.cleaned_data['password'])

        return password  # 校验通过的话就返回第二次输入的密码

def admin_add(request):
    title = '新建管理员'
    if request.method == 'GET':
        form = adminModelForm()
        return render(request,'change.html',{'form':form,'title':title})
    form = adminModelForm(data=request.POST)
    if form.is_valid():
        form.save()
        return redirect('/admin/list/')
    return render(request, 'admin_add.html', {'form': form})


class adminResetModelForm(BootStrapModelForm):

    confirm_password = forms.CharField(label='确认密码',max_length=64,
                                       widget=forms.PasswordInput(render_value=True))
    username = forms.CharField(disabled=True,label='用户名') ## 让用户名不可以编辑

    class Meta:
        model = models.Admin # 关联models.py中的表格
        fields = '__all__' # 可以用列表列出所有的列，也可以用这个来代表显示所有的列
        widgets = {
            'password':forms.PasswordInput(render_value=True)   ### 定义原来的密码输入框的样式也是：PasswordInput。手动生成的输入框在上面定义，自动生成的在这里定义样式
        }

    #### 利用字段的钩子方法,验证两次输入的密码是否一致。使用那个字段的钩子函数，就在那个字段的下面抛出异常提示
    def clean_confirm_password(self):
        password = self.cleaned_data.get('password')  ## 获取第一次输入的原密码。clean_data中保存着校验好的所有的数据
        ## 验证修改的密码是否跟现有的密码一致
        # 先去数据库中查询，当前编辑用户的密码
        exists = models.Admin.objects.filter(id=self.instance.pk,password=password).exists()
        if exists:
            raise ValidationError('密码不能跟原来的一样！')

        confirm_password = md5(self.cleaned_data.get('confirm_password')) ## 获取第二次输入的原密码
        if password != confirm_password:
            raise ValidationError('两次输入的密码不一致！')
        return confirm_password #校验通过的话就返回第二次输入的密码

    def clean_password(self):
        password = md5(self.cleaned_data.get('password'))

        return password  # 校验通过的话就返回第二次输入的密码


def admin_reset(request,nid):
    row_object = models.Admin.objects.filter(id=nid).first()
    title = '重置密码的用户：{}'.format(row_object.username)
    if request.method == 'GET':
        form = adminResetModelForm(instance=row_object)
        return render(request,'change.html',{'form':form,'title':title})
    form = adminResetModelForm(data=request.POST,instance=row_object)
    if form.is_valid():
        form.save()
        return redirect('/admin/list/')
    return render(request, 'change.html', {'form': form})


###### 登录函数
def login(request):

    if request.method == 'GET':
        form = LoginForm()
        return render(request,'login.html',{'form':form})
    form = LoginForm(data=request.POST)
    if form.is_valid():
        ## 获取用户输入的验证码：form.cleaned_data['code']。可以这样获取但是下面把**form.cleaned_data当参数传入到数据库
        ## 进行查询时，因为数据库中没有code这个字段就会报错。所以我们可以用pop特性：获取并在列表中弹出这个数据。
        user_input_code = form.cleaned_data.pop('code')
        ## 进行验证码的校验：在下面的函数：def image_code(request):已经把验证码存入到了session的image_code：request.session['image_code'] = code_string
        ## 所以可以通过调用函数def image_code(request):保存在session中的image_code和保存在form.cleaned_data['code']中的数据进行比较
        code = request.session.get('image_code')
        if code != user_input_code:
            form.add_error('code','验证码错误请重新输入')
            return render(request, 'login.html', {'form': form})
        # print(user_input_code)
        ## 本身form.cleaned_data就是一个字典，所以可以直接当作参数传到filter函数中
        admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
        if not admin_object:
            form.add_error('password','用户名或者密码错误请重新输入！')
            return render(request, 'login.html', {'form': form})
        ## 如果验证成功，那么就会把一个随机字符串写在session中和浏览器的cookie中
        ## 并在session中生成的随机字符串的后面，生成一个字典｛'info':form.cleaned_data.get('username')｝即info=登录验证的用户名
        ## 如果想储存多个数值的话，就把多个数值放在字典中，例如还想储存id
        request.session['info'] = {'id':admin_object.id,'username':admin_object.username}
        request.session.set_expiry(36000)
        # print(request.session['info'])

        # for item,v in request.session.items():
        #     print(item,v)

        return redirect('/admin/list/')
    return render(request, 'login.html', {'form': form})

    # username = request.POST.get('username')
    # password = request.POST.get('password')
    # data_dict = {'username':username,'password':password}
    # exists = models.Admin.objects.filter(**data_dict).exists()
    # if exists:
    #     return redirect('/admin/list/')
    # msg = '用户名或者密码错误，请重新输入！'
    # return render(request,'login.html',{'form':form,'msg':msg})


### 利用form生成两个输入框，并添加class="form-control"的样式
class LoginForm(BootStrapForm):
    username = forms.CharField(
        label='用户名',
        widget = forms.TextInput()
                               )
    password = forms.CharField(
        label='密码',
        widget = forms.PasswordInput(render_value = True)
                                )
    code = forms.CharField(
        label='验证码',
        widget = forms.TextInput()
                                )


    ### 编写钩子函数，验证用户输入的用户名和密码跟数据库中存储的是否一致
    def clean_password(self):
        password = md5(self.cleaned_data.get('password'))

        return password


def logout(request):

    request.session.clear()

    return redirect('/login/')


def image_code(request):

    img,code_string = check_code()

    ## 写入到自己的session中，以便后续获取验证码进行校验。并设置session超时时间60秒,让图片验证码过时就失效
    ## 这个session和login中的session是一个，只不过键不一样，数据格式是字典。session的所有设置都在里面
    request.session['image_code'] = code_string
    request.session.set_expiry(36000)
    # print(request.session['image_code'])
    stream1 = BytesIO() ## 创建内存对象
    img.save(stream1,'png') ## 把返回的图像文件写入内存对象中，格式为png
    # print(code_string)
    return HttpResponse(stream1.getvalue()) ## 获取内存中对象的值,并返回给调用的前端页面

#########  ajax
def task_list(request):

    form = TaskModelForm()
    return render(request,'task_list.html',{'form':form})

    # return render(request, 'task_list.html')

from django.views.decorators.csrf import csrf_exempt ## 取消csrf_token的认证，在需要取消的函数上面添加@csrf_exempt。主要用于ajax系统
import json ## 向ajax函数返回数据是json格式的数据
from django.http import JsonResponse ## 引入json数据的序列化模块，即把数据序列化称json格式。用这个模块就不用手动转换json数据了，
                                    ## 只需要添加一句return JsonResponse(data_dict),就会自动把数据转换成json格式
@csrf_exempt
def task_ajax(request):
    print(request.GET) ## 可以获取到前端页面通过ajax传递过来的数据。根据前端传递的模式，利用GET或者POST来进行接收
    print(request.POST)
    ## 向ajax函数返回的数据时json格式的
    data_dict = {'status':True,'data':[11,22,33,44,55,66]}
    # json_string = json.dumps(data_dict) ## 把要向前端传递的数据转换成json格式
    # return HttpResponse(json_string)
    return JsonResponse(data_dict)  ## 利用自带的模块，自动转换json

class TaskModelForm(BootStrapModelForm):
    class Meta:
        model = models.Task
        fields = '__all__'

@csrf_exempt
def task_add(request):
    # print(request.POST)
    form = TaskModelForm(data=request.POST)
    if form.is_valid():
        data_dict = {'status': True}
        return HttpResponse(json.dumps(data_dict))
    # print(form.errors.as_json())
    data_dict = {'status': False,'error':form.errors}
    return HttpResponse(json.dumps(data_dict,ensure_ascii=False))

####### order

class OrderModelForm(BootStrapModelForm):

    # oid = forms.CharField(disabled=True, label="订单号码") ## 重写oid列，让oid不能编辑
    # oid = forms.CharField(label="订单号码")
    class Meta:
        model = models.Order
        fields = '__all__'

def order_list(request):
    form = OrderModelForm()
    ## 根据日期时间动态生成订单号,并传递到前端，在前端生成的标签中的id属性值=id_oid的标签中显示
    import random
    from datetime import datetime
    ## 获取当前时间和四位数的随机字符串，并拼接在一起
    oid = datetime.now().strftime("%Y%m%d%H%M%S") + str(random.randint(1000, 9999))
    form.instance.oid = oid
    queryset = models.Order.objects.filter().order_by('-id')
    page_object = Pagination(request, queryset)
    page_queryset = page_object.page_queryset  ## 获取分完页面的数据
    page_string = page_object.html()  ## 获取生成的页码
    context = {
        'form': form,
        'queryset': page_queryset,
        'page_string': page_string
    }

    return render(request, 'order_list.html', context)
    # ## 根据日期时间动态生成订单号,并传递到前端，在前端生成的标签中的id属性值=id_oid的标签中显示
    # import random
    # from datetime import datetime
    # ## 获取当前时间和四位数的随机字符串，并拼接在一起
    # oid = datetime.now().strftime("%Y%m%d%H%M%S") + str(random.randint(1000, 9999))
    # return render(request,'order_list.html',{'form':form,"oid":oid})

@csrf_exempt
def order_add(request):
    form = OrderModelForm(data=request.POST)
    #form.instance.oid = oid ## form保存数据到数据库中时，读取的是form中clean_data中的数据，
                             # 所以当先赋值又在标签中修改数据后，保存的依然是修改的值。所以这个赋值只在不修改的时候生效。即订单号不可见时能用到
    # form.instance.admin_id = request.session['info']['id'] ## 还是当在框中修改是会保存修改后的数据，跟上面的oid一样
    # print(form.instance.admin_id)
    if form.is_valid():

        form.save()
        import random
        from datetime import datetime
        ## 获取当前时间和四位数的随机字符串，并拼接在一起
        oid = datetime.now().strftime("%Y%m%d%H%M%S") + str(random.randint(1000, 9999))
        data_dict = {'status': True,'oid':oid}
        return HttpResponse(json.dumps(data_dict))
    data_dict = {'status': False,'error':form.errors}
    return HttpResponse(json.dumps(data_dict, ensure_ascii=False))

def order_delete(request):
    uid = request.GET.get("uid")  ### 接收通过ajax用get方式传递过来的uid的数据
    exists = models.Order.objects.filter(id=uid).exists()
    if not exists:
        return JsonResponse({"status":False,"error":"数据不存在"})
    models.Order.objects.filter(id=uid).delete()
    return JsonResponse({"status":True})

def order_detail(request): ## 编辑订单
    uid = request.GET.get("uid")
    row_dict = models.Order.objects.filter(id=uid).values("oid","title","price","status","admin").first()
    if not row_dict:
        data_dict = {'status': False,'error':'数据不存在'}
        return JsonResponse(data_dict)
    result = {
        'status': True,
        'data':row_dict
    }
    return JsonResponse(result)


def upload_list(request):
    if request.method == 'GET':

        return render(request,'upload_list.html')
    ##　获取文件的对象。avatar是标签name的属性值
    file_object = request.FILES.get("avatar")
    ## 获取文件名
    print(file_object.name)

    ## 获取文件的内容
    f = open('aa.txt',mode='wb')
    for chunk in file_object.chunks():
        f.write(chunk)
    f.close()

    print(request.POST) ## 连csrfmiddlewaretoken的数据也能获取到
    # print(request.FILES)
    return redirect('/upload/list/')

class UpForm(BootStrapForm):
    name = forms.CharField(label='姓名')
    age = forms.IntegerField(label='年龄')
    img = forms.FileField(label='头像')
    exclude_field = ['img'] ## 排除不使用样式的字段


def upload_form(request):
    title1 = 'Form上传文件'
    if request.method == 'GET':
        form = UpForm()
        return render(request,'upload_form.html',{'form':form,'title':title1})
    form = UpForm(data=request.POST,files=request.FILES) ## 获取通过POST推过来的数据和文件数据
    if form.is_valid():
        # print(form.cleaned_data)
        image_object = form.cleaned_data.get("img")
        # file_path = "app_01/static/img/｛｝".format(image_object.name)  ## 保存文件的路径，文件名用格式化的文件字符串来获取上传文件的文件名
        # db_file_path = os.path.join("static","img",image_object.name)  ## 数据库中的存储路径
        # file_path = os.path.join("app_01",db_file_path) ## 读文件的路径
        from django.conf import settings
        ## 使用新建的根目录下面的media文件夹，来当作保存文件的路径
        # media_path = os.path.join(settings.MEDIA_ROOT, image_object.name) 绝对路径
        media_path = os.path.join("media", image_object.name)  ## 相对路径
        f = open(media_path,'wb')
        for chunk in image_object.chunks():
            f.write(chunk)
        f.close()

        ## 将完整的的文件路径保存到数据库的字段image中，并将其余的两个字段也写入数据库对应的列中
        models.Boss.objects.create(
            name=form.cleaned_data.get("name"),
            age=form.cleaned_data.get("age"),
            img=media_path
        )


        return HttpResponse('........')
    return render(request, 'upload_form.html', {'form': form, 'title': title1})

class UpModelForm(BootStrapModelForm):
    exclude_field = ['img']
    class Meta:
        model = models.City
        fields = '__all__'

def upload_modal_form(request):
    title = 'ModolForm上传文件'
    if request.method == 'GET':
        form = UpModelForm()
        return render(request,'upload_form.html',{'form':form,'title':title})
    form = UpModelForm(data=request.POST,files=request.FILES)
    if form.is_valid():
        form.save()
        return HttpResponse('.......')

    return render(request, 'upload_form.html', {'form': form, 'title': title})


def city_list(request):
    queryset = models.City.objects.filter()
    return render(request,'city_list.html',{'queryset':queryset})

# def ret(request):
#     res = models.PrettyNum.objects.filter(id__gt=F('price'))
#     ree = models.PrettyNum.objects.filter(id__gt=F('price')*2 + 20)




